package socket

import (
	"bytes"
	"gitee.com/sdynasty/clever/clog"
	"gitee.com/sdynasty/clever/util"
	"encoding/binary"
	"errors"
	"github.com/golang/protobuf/proto"
	"net"
	"sync"
)


const (
	CONST_HEADER        	= "SDY"
	CONST_HEADER_LENGTH 	= 3
	CONST_DATA_LENGTH       = 4
	CONST_CMD_LENGTH    	= 2
)


type ConnectionNet struct{
	fd             uint32
	netConnect     net.Conn
	inChan         chan []byte
	outChan        chan []byte
	closeChan      chan byte
	mq             chan ChanMessage
	messageHandler IMessageHandler
	concurrency    bool

	mutex sync.Mutex  // 对closeChan关闭上锁
	isClosed bool  // 防止closeChan被关闭多次
}

// 初始化连接
func InitNetConnection (netConn net.Conn, fd uint32, mq chan ChanMessage) (connection *ConnectionNet, err error) {
	connection = &ConnectionNet{
		fd : fd,
		netConnect: netConn,
		inChan: make(chan []byte, 1000),
		outChan: make(chan []byte, 1000),
		closeChan: make(chan byte, 1000),
		mq : mq,
	}

	// 启动读写协程
	go connection.readLoop()
	go connection.writeLoop()

	return
}

// 关闭
func (conn *ConnectionNet) Close() {
	// 线程安全， 可以多次调用
	conn.netConnect.Close()
	// 利用标记，让closeChan只关闭一次
	conn.mutex.Lock()
	if !conn.isClosed {
		close(conn.closeChan)
		conn.isClosed = true

		// 通知上层网络关闭
		chanMes := ChanMessage{
			Fd:       conn.fd,
			Data:     nil,
			DataType: SocketDataType_NetBroken,
		}
		conn.mq <- chanMes
	}
	conn.mutex.Unlock()
}

func (conn *ConnectionNet) WriteProto(msg proto.Message, cmd uint16) {
	clog.LogLogic().Printf("writeProto, cmd:%d, msg:%s", cmd, msg.String())
	data := util.EncodePb(msg, cmd, false)
	conn.WriteMessage(data)
}

// 写入消息
func (conn *ConnectionNet) WriteMessage(data []byte) (err error) {
	select {
	case conn.outChan <- data:
	case <-conn.closeChan:
		err = errors.New("connection is closed")
	}

	return
}

// 读循环
func (conn *ConnectionNet) readLoop() {
	var (
		n int
		data []byte
		err error
		tempBuff = make([]byte, 0)
		fullData [][]byte
	)

	for {
		data = make([]byte, 1024)
		if n, err = conn.netConnect.Read(data); err != nil {
			clog.LogNetwork().Error("conn.netConnect.Read err : ", err)
			goto ERR
		}

		// 处理粘包情况
		tempBuff = append(tempBuff, data[:n]...)
		fullData, tempBuff = util.GetMultiPacks(tempBuff)

		for _, data := range fullData {
			chanMes := ChanMessage{
				Fd:       conn.fd,
				Data:     data,
				DataType: SocketDataType_Normal,
			}

			// 直接处理, 不再转队列
			if conn.concurrency {
				go conn.messageHandler.Handle(conn.fd, data, SocketDataType_Normal)
			} else {
				conn.messageHandler.Handle(conn.fd, data, SocketDataType_Normal)
			}

			select {
			case conn.mq <- chanMes:
			case <- conn.closeChan:
				clog.LogNetwork().Info("conn.closeChan")
				goto ERR
			}
		}
	}
ERR:
	conn.Close()
}

// 写循环
func (conn *ConnectionNet) writeLoop() {
	var (
		data []byte
		err error
	)

	for {
		select {
		case data= <- conn.outChan:
		case <- conn.closeChan:
			goto ERR
		}
		if _, err = conn.netConnect.Write(data); err != nil {
			goto ERR
		}
	}
ERR:
	conn.Close()
}


//解包
func (conn *ConnectionNet) DePack(buff []byte) ([]byte, []byte, error) {
	length := len(buff)
	//如果包长小于header 就直接返回 因为接收的数据不完整
	if length < CONST_HEADER_LENGTH+CONST_DATA_LENGTH {
		return buff, nil, nil
	}

	//如果header不是 指定的header 说明此数据已经被污染 直接返回错误
	if string(buff[:CONST_HEADER_LENGTH]) != CONST_HEADER {
		return []byte{}, nil, errors.New("header is not safe")
	}

	// 数据字节长度不够
	msgLength := conn.BytesToInt(buff[CONST_HEADER_LENGTH : CONST_HEADER_LENGTH+CONST_DATA_LENGTH])
	if length < CONST_HEADER_LENGTH+CONST_DATA_LENGTH+ msgLength {
		return buff, nil, nil
	}

	// 完整的数据
	data := buff[CONST_HEADER_LENGTH+CONST_DATA_LENGTH : CONST_HEADER_LENGTH+CONST_DATA_LENGTH+msgLength]
	buffs := buff[CONST_HEADER_LENGTH+CONST_DATA_LENGTH+msgLength:]
	return buffs, data, nil
}

func (conn *ConnectionNet) IntToBytes(n int) []byte {
	x := int32(n)
	bytesBuffer := bytes.NewBuffer([]byte{})
	binary.Write(bytesBuffer, binary.BigEndian, x)
	return bytesBuffer.Bytes()
}

func (conn *ConnectionNet) BytesToInt(b []byte) int {
	bytesBuffer := bytes.NewBuffer(b)
	var x int32
	binary.Read(bytesBuffer, binary.BigEndian, &x)
	return int(x)
}